#include "Sala.h"
#include "Lock.h"
#include "../Common/ConditionVariable.h"
#include "ManejadorDeJugadores.h"
#include <unistd.h>
#define MINIMO_JUGADORES 2

Sala::Sala(unsigned maxJugadores, unsigned nivel, ArchivoUsuarios& archUsuarios)
:nivel(nivel), juego(NULL), cantJugadoresConectados(0),
 maximoJugadores(maxJugadores), estado(enEspera), estaCorriendo(false),
 cantEnviosRealizados(0), archivoUsuarios(archUsuarios)
{
}

bool Sala::agregarJugador(Cliente* cliente)
{
	Lock lock(mutex);
	bool loAgrego = false;
	if(cantJugadoresConectados < maximoJugadores) {
		clientes.push_back(cliente);
		cantJugadoresConectados++;
		loAgrego = true;
		conditionVariable.signal();//Despierta el esperarCompletarse()
	}
	return loAgrego;
}

void Sala::startMethod()
{
	estaCorriendo = true;

	//Espera a que se llene la sala
	esperarCompletarse();

	for(unsigned i = 0; i < clientes.size() && estaCorriendo; i++){
		JugadorEnviar* jugadorDeEnvio = new JugadorEnviar(*clientes[i],
				vectorDeEnvios, mutex, conditionVariable, cantEnviosRealizados);
		JugadorRecibir* jugadorDeRecepcion = new JugadorRecibir(*clientes[i], i, colaDeRecepciones);

		jugadorDeEnvio->start();
		jugadorDeRecepcion->start();

		jugadoresDeEnvio.push_back(jugadorDeEnvio);
		jugadoresDeRecepcion.push_back(jugadorDeRecepcion);
	}

	ManejadorDeJugadores manejadorDeJugadores(clientes, mutex,
			cantJugadoresConectados, estaCorriendo);
	manejadorDeJugadores.start();

	//Espera a que los jugadores den el ok
	esperarConfirmaciones();

	jugar();

	manejadorDeJugadores.matar();
	manejadorDeJugadores.join();

	for(unsigned i = 0; i < jugadoresDeEnvio.size(); i++){
		jugadoresDeEnvio[i]->matar();
		jugadoresDeRecepcion[i]->matar();
	}

	//Despierto jugadores de recepcion dormidos
	conditionVariable.broadcast();

	for(unsigned i = 0; i < jugadoresDeEnvio.size(); i++){
		jugadoresDeEnvio[i]->join();
		jugadoresDeRecepcion[i]->join();

		delete jugadoresDeEnvio[i];
		delete jugadoresDeRecepcion[i];
	}

	estado = finalizada;
}

void Sala::matar()
{
	estaCorriendo = false;
	conditionVariable.broadcast();
}

Estado Sala::obtenerEstado()
{
	return estado;
}

Json::Value Sala::getJson()
{
	Json::Value root;
	root["Nivel"] = nivel; //aca va el nivel del juego
	root["MaximoJugadores"] = maximoJugadores;
	root["CantJugadoresConectados"] = cantJugadoresConectados;
	return root;
}

void Sala::esperarCompletarse()
{
	do{
		Json::Value root = getJson();
		root["Mensaje"] = "Esperando completar sala";
		for(unsigned i = 0; i < clientes.size(); i++){
			clientes[i]->enviar(root);
		}
		Lock lock(mutex);
		conditionVariable.wait(mutex);
	}while((cantJugadoresConectados < maximoJugadores) && estaCorriendo);

	if(estaCorriendo){
		Json::Value root = getJson();
		root["Mensaje"] = "Sala completa";
		for(unsigned i = 0; i < clientes.size(); i++)
			clientes[i]->enviar(root);

		estado = completa;
	}
}

void Sala::esperarConfirmaciones()
{
	Json::Value root;
	root["Mensaje"] = "Enviar confirmacion";

	//Les avisa a todos los jugadores que les envie la confirmacion para arrancar
	vectorDeEnvios.pushBack(&root);

	enviar();

	std::vector<bool> confirmaciones;
	for(unsigned i = 0; i < jugadoresDeRecepcion.size(); i++)
		confirmaciones.push_back(false);

	//Mientras no haya rebido nada se queda en el while
	while ( (colaDeRecepciones.size() != confirmaciones.size()) && estaCorriendo)
		sleep(3);

	vectorDeEnvios.vaciar();
}

void Sala::jugar()
{
	limpiarCola(); // Por si quedo algo

	enviarDatosDeInicio();

	unsigned idJugador = 0;
	while(juego->hayMasMovimientos() && !hayGanador(idJugador) && estaCorriendo){

		pedirMovimiento();
		bool movimientoValido = false;

		while(!movimientoValido && estaCorriendo){

			//Espera recibir un movimiento
			while(colaDeRecepciones.empty() && estaCorriendo) sleep(2);

			if(!colaDeRecepciones.empty()){
				Movimiento mov;
				obtenerMovimiento(mov, idJugador);

				movimientoValido = juego->mover(mov);

				if(movimientoValido){
					obtenerTableros();
					obtenerPuntajes(idJugador);
					limpiarCola();//Desecha otros posibles movimientos
					enviar();

					for(unsigned i = 0; i < vectorDeEnvios.size(); i++)
						delete vectorDeEnvios[i];
					vectorDeEnvios.vaciar();

				}
			}
		}
	}
	finDelJuego(idJugador);
}

void Sala::setEstado(Estado estado)
{
	this->estado = estado;
}

void Sala::enviar()
{
	//Pone en cero el contador
	cantEnviosRealizados = 0;

	//Despierto los threads para que envien
	conditionVariable.broadcast();

	//Espera a todos hayan enviado
	bool espera = true;
	while(espera && estaCorriendo){
		sleep(2);
		Lock lock(mutex);
		espera = (cantEnviosRealizados < cantJugadoresConectados);
	}
}

void Sala::limpiarCola(){
	while(!colaDeRecepciones.empty()){
		Json::Value* root = colaDeRecepciones.front();
		delete root;
		colaDeRecepciones.pop();
	}
}

void Sala::obtenerMovimiento(Movimiento& mov, unsigned& idJugador)
{
	Json::Value* rootMov = colaDeRecepciones.front();

	mov.setJson(*rootMov);

	idJugador = (*rootMov)["Id"].asUInt();

	delete rootMov;

	colaDeRecepciones.pop(); // Por si quedo algo
}

void Sala::obtenerTableros()
{
	while(juego->hayTableroParaEnviar()){

		Json::Value* root = new Json::Value();

		(*root)["Accion"] = "Mostrar tablero";

		(*root)["Tablero"] = juego->obtenerTablero();

		(*root)["Maximo score"] = juego->getPuntajeMaximo();

		vectorDeEnvios.pushBack(root);
	}
}

void Sala::obtenerPuntajes(unsigned& idJugador)
{
	clientes[idJugador]->incrementarPuntaje(juego->obtenerPuntaje());

	Json::Value* root = new Json::Value();

	(*root)["Accion"] = "Mostrar puntajes";

	(*root)["CantDeJugadores"] = cantJugadoresConectados;

	unsigned j = 0; // indice para cliente conectado
	for(unsigned i = 0; i < clientes.size(); i++){
		if(clientes[i]->estaConectado()){
			(*root)["Jugadores"][j]["Nombre"] = clientes[i]->obtenerNombreDeUsuario();
			(*root)["Jugadores"][j]["Puntaje"] = clientes[i]->getPuntaje();
			j++;
		}
	}

	vectorDeEnvios.pushBack(root);
}

void Sala::pedirMovimiento()
{
	Json::Value root;
	root["Accion"] = "Enviar movimiento";

	vectorDeEnvios.pushBack(&root);

	enviar();

	vectorDeEnvios.vaciar();
}

void Sala::finDelJuego(unsigned & idJugador)
{
	if(cantJugadoresConectados > 0){
		Json::Value root;
		root["Accion"] = "Fin";

		if(cantJugadoresConectados > 1){
			if(hayGanador(idJugador)){
				root["Ganador"] = clientes[idJugador]->obtenerNombreDeUsuario();
				clientes[idJugador]->incrementarNivel(nivel);
				archivoUsuarios.guardarUsuario(clientes[idJugador]->getUsuario());
			}
			else{
				root["Ganador"] = "Juego perdido";
			}

			vectorDeEnvios.pushBack(&root);
			enviar();
			vectorDeEnvios.vaciar();
		}
		else{
			//Busca el unico jugador que quedo en sala y si quedo con
			//el 80% del puntaje maximo a alcanzar gano el juego
			unsigned i = 0;
			while(!clientes[i]->estaConectado() && i < clientes.size())
				i++;

			if(clientes[i]->estaConectado()){
				if(clientes[i]->getPuntaje() > (juego->getPuntajeMaximo()*0.8)){
					root["Ganador"] = clientes[idJugador]->obtenerNombreDeUsuario();
					clientes[idJugador]->incrementarNivel(nivel);
					archivoUsuarios.guardarUsuario(clientes[idJugador]->getUsuario());
				}
				else
					root["Ganador"] = "Juego perdido";
				clientes[i]->enviar(root);
			}
		}
	}
}

unsigned Sala::getNivel() const
{
	return this->nivel;
}

Sala::~Sala()
{
	delete juego;
}

bool Sala::hayGanador(unsigned & idJugador)
{
	return (clientes[idJugador]->getPuntaje() >= juego->getPuntajeMaximo());
}

void Sala::enviarDatosDeInicio()
{
	CargaJson levantador;
	Json::Value root;
	std::stringstream s;
	s << "nivel_" << nivel << ".json";
	std::string sNivel = s.str();
	levantador.getJson(sNivel,root);

	std::string imagenFondo = root["imagen de fondo"].asString();
	Json::Value rootImagenFondo;
	if(imagenFondo != ""){
		rootImagenFondo["Accion"] = "Imagen fondo personalizada";
		rootImagenFondo["Binario Fondo"] = root["Binario Fondo"];
	}
	else{
		rootImagenFondo["Accion"] = "Imagen fondo default";
	}

	vectorDeEnvios.pushBack(&rootImagenFondo);


	std::string imagenAgujero = root["imagen de agujero"].asString();
	Json::Value rootImagenAgujero;
	if(imagenAgujero != ""){
		rootImagenAgujero["Accion"] = "Imagen agujero personalizada";
		rootImagenAgujero["Binario agujero"] = root["Binario Agujero"];
	}
	else{
		rootImagenAgujero["Accion"] = "Imagen agujero default";
	}

	vectorDeEnvios.pushBack(&rootImagenAgujero);

	enviar();
	vectorDeEnvios.vaciar();

	juego = new Juego(root);

	//Envio el tablero inicial
	obtenerTableros();
	enviar();

	delete vectorDeEnvios[0];
	vectorDeEnvios.vaciar();

}
